package org.mashart.pink.m.gps.data;

import org.mashart.pink.m.gps.Waypoint;

/**
 * Assembler which accepts incoming NMEA sentences and produces waypoints.
 *
 * The assembler accepts the NMEA sentences with multiple formats from a GPS device,
 * collects different types of information of that point. E.g., by check the timestamp,
 * the assembler can take altitude from GPGGA-format sentence, speed from GPRMC-format sentence,
 * to assemble a single waypoint.
 *
 * The assembler assumes that the NMEA are coming in with wide-sense increasing timestamp.
 * If an incoming sentence has an older timestamp than the latest timestamp that the assembler
 * has ever seen, this older sentence will be ignored. For those sentences with no timestamp.
 * It is assumed that they have the same timestamp as the last received sentence.
 */
public class NMEAAssembler {

    /**
     * The waypoint evolving, i.e., being assembled
     */
    private Waypoint evlWp;

    /**
     * The waypoint that is fixed, i.e, ready to output
     */
    private Waypoint fxdWp;

    /**
     * Latest timestamp of the input NMEA sentences
     */
    private int ts;

    /**
     * NMEA parser
     */
    private NMEAParser parser;

    /**
     * Constructor
     */
    public NMEAAssembler(int acc){
        parser = new NMEAParser(acc);
        evlWp = null;
        fxdWp = null;
        // -1 means just initialzed, not waypoint received so far
        ts = -1;
    }

    /**
     * Put a NMEA sentence into the assembler
     * This method is not synchronized with "get" or "clear" method.
     * The external usage should make sure no concurrent invocations are possible.
     */
    public void put(String snt){
        // if the sentence is not valid
        if(snt == null || !parser.isValid(snt)){
            return;
        }
        // parse the timestamp of the input NMEA sentence
        int snt_ts = parser.getTimeMS(snt);
        // sentence with no timestamp. Assume this sentence is generated at the same time as the evolving waypoint, if there is an evolving waypoint.
        if(snt_ts < 0){
            if(evlWp != null){
                // updates the evolving waypoint
                evlWp = parser.update(evlWp, snt);
            }
        }
        else{
            // calculate the difference between timestamp of NMEA sentence and the assembler
            int diff = diffTime( snt_ts, this.ts);

            // if there is no evolving waypoint, i.e., no waypoint is being assembled
            if(evlWp == null){
                // if the sentence is newer than the timestamp of the assembler, create a new evolving waypoint
                // or if the assembler is just initialzed, no waypoint received so far
                if(diff > 0 || ts < 0 ){
                    // create a new evolving waypoint
                    evlWp = parser.parse(snt);
                    // update the timestamp of the assembler
                    ts = snt_ts;
                }
                // ELSE: if the sentence is older than the timestamp of the assembler, it comes too late;
                // or if the sentence has the same timestamp with the assembler, it is also too late,
                // because the waypoint with the same timestamp already left the evolving stage, (it is fixed or read out)
            }
            // there is an evolving waypoint
            else{
                // if the sentence is newer
                if(diff > 0){
                    // push the evolving waypoint into the fixed position
                    // the previous fixed waypoint is discarded
                    fxdWp = evlWp;
                    // parse the sentence and create a new evolving waypoint
                    evlWp = parser.parse(snt);
                    // update the timestamp of the assembler
                    ts = snt_ts;
                }
                // if the sentence is with the same timestamp with assembler,
                // then it must be the same with the evolving waypoint
                else if(diff == 0){
                    // updates the evolving waypoint
                    evlWp = parser.update(evlWp, snt);
                }
                // ELSE: if the sentence is older, discard it
            }
        }
    }

    /**
     * Get a waypoint. Return null if no waypoint is fixed
     * This method is not synchornized with "put" method. It is possible to return a transient reference of "fixed waypoint",
     * e.g, in some stage inside a "put" operation, a waypoint is pushed from "evolving" to "fixed", but the new evolving waypoint is not created yet.
     * The "get" method only read a reference of "fixed waypoint", the result is guaranteed to be a valid waypoint,
     * because the "fixed waypoint" will not be updated any more, given that concurrent "put" are impossible.
     */
    public Waypoint get(){
        // copy and remove the fixed waypoint
        Waypoint wp = fxdWp;
        fxdWp = null;
        return wp;
    }

    /**
     * Clear the assembler. Set both the evolving waypoint and fixed waypoint to null.
     * This method is not synchornized with "put" or "get" method.
     * The external usage should make sure no concurrent invocations are possible.
     */
    public void clear(){
        evlWp = null;
        fxdWp = null;
    }

    /**
     * Calculate the difference of two timestamp. If the absolute value of difference is greater than 22 hours,
     * we assume that these two timestamps respectively belong to two seqential days, and apply corresponding amendment.
     * @param ta timestamp a, in milliseconds
     * @param tb timestamp b, in milliseconds
     * @return the difference of two timestamps. If ta follows tb, return positive integer; if ta preceeds tb, return negative integer; if ta concurs tb, return 0.
     * The absolute value of returned value equals the elapsed milliseconds between ta and tb.
     */
    public static int diffTime(int ta, int tb){
        // 1 hour = 3600000 milliseconds
        int h = 3600000;
        // threshold of judging whether two timestamps belong to two sequential days
        int th = 22 * h;

        // difference
        int diff = ta - tb;

        // apply amendment if necessary
        if(diff > th){
            diff -= 24 * h;
        }
        else if(diff < -th){
            diff += 24 * h;
        }

        return diff;
    }

}
